//
// Created by denglibin on 2021/9/10.
//
#include "04_socket.h"
#include "init.h"
#include "log.h"
#include <stdlib.h>
#ifndef _WIN32
#include <fcntl.h>
#include <sys/types.h>
#include<unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#endif
#define SPORT 5001

/**
 * 新的连接回调
 * @param s
 * @param which
 * @param arg
 */
static void listen_cb(evutil_socket_t s, short which, void* arg);
/**
 * 客户端 回调
 * @param s
 * @param which
 * @param arg
 */
static void client_cb(evutil_socket_t s, short which, void* arg);

/**
 * ctrl_c 回调
 * @param sock
 * @param which
 * @param arg
 */
static void ctrl_c(int sock, short which, void* arg);

static void listen_cb(evutil_socket_t s, short which, void* arg){
    zlog_info(log_get_zlog_c(), "listen_cb");

    struct sockaddr_in client_addr;//客户端的ip和端口
    socklen_t sin_size = sizeof(struct sockaddr_in);
    //获取连接读取连接信息
    evutil_socket_t client  = accept(s, (struct sockaddr*)&client_addr, &sin_size);
    if(client < 0){
        perror("accept");
        zlog_error(log_get_zlog_c(), "accept fail");
        return;
    }
    //获取客户端的端口和地址
    char client_ip_buf[16] = {0};
    //inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip_buf, sizeof(client_ip_buf) - 1);
    evutil_inet_ntop(AF_INET, &client_addr.sin_addr, client_ip_buf, sizeof(client_ip_buf) - 1);
    zlog_info(log_get_zlog_c(), "client ip = %s, port = %d\n", client_ip_buf, ntohs(client_addr.sin_port));
    struct event_base* base = (struct event_base*) arg;

    ////水平触发的话，只要有数据没处理，会一直调用调用回调函数
    ////边沿触发，只调用一次，如果数据没处理完，也不会调用，只有等到下次有新数据到时，再调用 （不会频繁调用，性能好一点）
    //默认水平触发
    //struct event* ev = event_new(base, client, EV_READ|EV_PERSIST, client_cb, event_self_cbarg());
    //边沿触发
    struct event* ev = event_new(base, client, EV_READ|EV_PERSIST|EV_ET, client_cb, event_self_cbarg());
    struct timeval t = {10, 0};
    event_add(ev, &t);
}

static void client_cb(evutil_socket_t s, short which, void* arg){

    struct event* ev = (struct event*) arg;
    //判断超时
    if(which & EV_TIMEOUT) {
        zlog_info(log_get_zlog_c(), "time out");
        event_free(ev);
        evutil_closesocket(s);
        return;
    }
    //读取客户端发过来的数据
    char buf[1024] = {0};
    int len = recv(s, buf, sizeof(buf) - 1, 0);
    if(len > 0) {
        zlog_info(log_get_zlog_c(), "\n%s", buf);
        char* response = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\nContent-Type: text/html\r\n\r\nHello World!";
        send(s, response, strlen(response), 0);
    }else {
        zlog_info(log_get_zlog_c(), "event_free");
        event_free(ev);
        evutil_closesocket(s);
    }
}

static void ctrl_c(int sock, short which, void* arg){
   struct event_base* base = (struct event_base*)arg;
   //退出事件循环（执行完当前处理的事件就退出）
   //event_base_loopbreak(base);

   //运行完所有的活动事件再退出
   struct timeval t = {3, 0};
   event_base_loopexit(base, &t); //至少再运行3秒
}

void demo_04(){
#ifndef _WIN32
    init();
    zlog_info(log_get_zlog_c(), "socket...");
    struct event_base* base = event_base_new();
    if(!base){
        clean();
        perror("event_base_new");
        zlog_error(log_get_zlog_c(), "event_base_new fail");
        return;
    }
    //创建socket
    evutil_socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock <= 0){
        clean();
        perror("socket");
        zlog_error(log_get_zlog_c(), "event_base_new fail");
        return;
    }
    //设置地址复用和非阻塞
    if(evutil_make_listen_socket_reuseable(sock) < 0){
        clean();
        perror("evutil_make_listen_socket_reuseable");
        zlog_error(log_get_zlog_c(), "evutil_make_listen_socket_reuseable fail");
        return;
    }
    if(evutil_make_socket_nonblocking(sock) < 0){
        clean();
        perror("evutil_make_socket_nonblocking");
        zlog_error(log_get_zlog_c(), "evutil_make_socket_nonblocking fail");
        return;
    }


    struct sockaddr_in sin;
    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET; //协议域
    sin.sin_port = htons(SPORT);//端口 htons是将整型变量从主机字节顺序转变成网络字节顺序， 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处(大端序) s表示short 16位
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    //绑定地址和端口
    int ret = bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr));
    if(ret != 0){
        clean();
        perror("bind");
        zlog_error(log_get_zlog_c(), "bind fail");
        return;
    }
    //开始监听
    // 参数1：套接字文件描述符， 侦听队列长度
    if(listen(sock, 8) < 0){
        clean();
        perror("bind");
        zlog_error(log_get_zlog_c(), "listen fail");
        return;
    }
    //添加事件，默认水平触发
    struct event* ev = event_new(base, sock, EV_READ|EV_PERSIST, listen_cb, base);
    event_add(ev, NULL);

    struct event* csig = evsignal_new(base, SIGINT, ctrl_c, base);
    if(!csig){
        clean();
        fprintf(stderr, "SIGINT evsignal_new() fail\n");
        return;
    }
    event_add(csig, NULL);


     //进入事件主循环
    event_base_dispatch(base);
    //EVLOOP_ONCE: 等待一个事件运行，直到没有活动事件就退出
    //EVLOOP_NONBLOCK: 有活动事件就处理，没有就返回0 （自己写到循环中）
    //EVLOOP_NO_EXIT_ON_EMPTY: 没有注册事件也不返回，阻塞，用于其他线程添加事件
    //event_base_loop(base, EVLOOP_NO_EXIT_ON_EMPTY);

    event_base_free(base);
    close(sock);
    clean();
#endif
}